{
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.8"
  },
  "orig_nbformat": 2,
  "kernelspec": {
   "name": "python378jvsc74a57bd0e7a6a2a0faef9b3dca8eee8d2f0177f3278be1cb967e7fc435687a2394b4cb7a",
   "display_name": "Python 3.7.8 64-bit"
  },
  "metadata": {
   "interpreter": {
    "hash": "e7a6a2a0faef9b3dca8eee8d2f0177f3278be1cb967e7fc435687a2394b4cb7a"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2,
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "Algos in Python\n",
    "================\n",
    "\n",
    "This document covers how to instantiate an abstract algorithm, and then configure it with a config block\n",
    "\n",
    "Instantiating an abstract algo in Python is very similar to instantiating an algo in C++.\n",
    "\n",
    "A note about providing a custom algo/arrow implementation, \n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "The first step in this process is to actually provide an instantiation for the given abstract algo.\n",
    "In this tutorial we assume that the selected algo is one provided by the existing Kwiver Python bindings.\n",
    "Implementing an abstract algo in Python and then instantiating it is slightly more complex and covered in a later piece of documentation.\n",
    "\n",
    "\n",
    "Below we have selected the Image IO C++ algo to instantiate"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital.algo import ImageIO\n",
    "\n",
    "class CustomImageIOImpl(ImageIO):\n",
    "    \"\"\"\n",
    "    Custom implementation of the Abstract Kwiver Vital algo ImageIO\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        # THE FOLLOWING LINE IS REQUIRED BY PYBIND11\n",
    "        super.__init__(self)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "Notice that we subclass the algorithm that we wish to instantiate. Additionally, note that we call the initialization function for the parent (the desired algo) class. This is required by PyBind11 to ensure proper inheritance across languages.\n",
    "\n",
    "Next we expose the algorithm for registration"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "def __vital_algorithm_register__():\n",
    "    from kwiver.vital.algo import algorithm_factory as af\n",
    "    \n",
    "    # This name is the name of the class you used to instantiate the algo\n",
    "    # the class name can be anything, but this string needs to mirror that\n",
    "    inst_name = \"CustomImageIOImpl\"\n",
    "    # determine if algo is already loaded\n",
    "    if af.has_algorithm_impl_name(\n",
    "        CustomImageIOImpl.static_type_name(),\n",
    "        inst_name\n",
    "    ):\n",
    "        return\n",
    "    af.add_algorithm( inst_name,\n",
    "                     \"kwiver.vital.algo.ImageIO\",\n",
    "                     CustomImageIOImpl,\n",
    "                     )\n",
    "    af.mark_algorithm_as_loaded( inst_name )"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "This registration block is very similar to the process for registering a C++ algo inst during runtime. Here we leverage python's special treatment of dunder (or magic if you prefer) methods to inform Kwiver of this algo instantiation. \n",
    "\n",
    "Note that at this point, our algo implementation, while useable, is still abstract as we have not provided concrete implementations of any of the exposed methods.\n",
    "\n",
    "Additionally, we need to expose and implement the ability to configure the algo\n",
    "with Kwiver config blocks. These are supported in Python via bindings that expose the config block under `kwiver.vital.config`\n",
    "\n",
    "Our class then, fully implemented might look something like this."
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital.algo import ImageIO\r\n",
    "import PIL\r\n",
    "\r\n",
    "class CustomImageIOImpl(ImageIO):\r\n",
    "    \"\"\"\r\n",
    "    Custom implementation of the Abstract Kwiver Vital algo ImageIO\r\n",
    "    \"\"\"\r\n",
    "    def __init__(self):\r\n",
    "        super.__init__(self)\r\n",
    "        # initialize any member attributes\r\n",
    "    \r\n",
    "    def get_configuration(self):\r\n",
    "        return super().get_configuration()\r\n",
    "\r\n",
    "    def set_configuration(self, conf_c = None):\r\n",
    "        cnf = self.get_configuration()\r\n",
    "        cnf.merge_config(cnf, conf_c)\r\n",
    "    \r\n",
    "    def load(im):\r\n",
    "        return PIL.Image.open(im)\r\n",
    "        \r\n",
    "    def save(im):\r\n",
    "        im.save(\"PIL_im.png\")\r\n",
    "\r\n"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "Arrows/algos in Python can be executed by a pipeline via a Sprokit Process, however they can also be called directly from Python code.\n",
    "\n",
    "The example below demonstrates this style of useage. This is of course, a very basic example, but it demonstrates enough to implement a custom algo and execute it via python."
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital import types as kvt\r\n",
    "from kwiver.vital import algo\r\n",
    "im_dir = \"./shapes.png\"\r\n",
    "\r\n",
    "PIL_io = algo.ImageIO.create(\"CustomImageIOImpl\")\r\n",
    "im = PIL_io.load(im_dir)\r\n",
    "# operations can now be performed on the image as if it were a typical OCV python image, because it is. This algo is basically just a very thin wrapper around some basic opencv functionality\r\n",
    "kvt.BoundingBoxD(0,10,0,25)\r\n",
    "\r\n",
    "# alternatively all this can be done via the algorithm factory\r\n",
    "\r\n",
    "import kwiver.vital.algo.algorithm_factory as af\r\n",
    "PIL_io2 = af.create_algorithm(\"ImageIO\",\"CustomImageIOImpl\")"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "Algorithms can be instantiated and configured programmatically during runtime via configurations (as opposed to Sprokit Processes).\n",
    "\n",
    "The following examples shows how the algorithm implemented above would be programmatically instantiated and configured"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital.config import config\r\n",
    "from kwiver.vital import algo\r\n",
    "        \r\n",
    "\r\n",
    "# First we need to get a reference to the base algo type we want to instantiate\r\n",
    "PIL_io = algo.ImageIO\r\n",
    "\r\n",
    "# Next we must load a config_block object\r\n",
    "# Currently this cannot be done from file in Python\r\n",
    "# But we can construct a config block programmatically\r\n",
    "# So lets assume we've done that\r\n",
    "\r\n",
    "io_conf = load_config(conf_dir)\r\n",
    "if algo.ImageIO.check_nested_algo_configuration(\"IO\",io_conf):\r\n",
    "     algo.ImageIO.set_nested_algo_configuration(\"IO\",io_conf, PIL_io)\r\n",
    "else:\r\n",
    "    raise RuntimeError(\"Unable to properly load conf\")\r\n",
    "\r\n",
    "# Now PIL_io holds our instantiated algorithm\r\n",
    "# Ready to go and configured by a conf object\r\n",
    "# The config syntax does not change between c++ and Python\r\n"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "C++ arrow from Python\r\n",
    "\r\n",
    "Arrows currently existing with a C++ implementation can be accessed and utilized directly from Python code with no need for any extra steps.\r\n",
    "The process is, much like above, simply loading in the arrow for use, almost exactly the way it's done by C++"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital.algos import algorithm_factory as af\r\n",
    "from kwiver.vital.config import config\r\n",
    "\r\n",
    "uuid_mod = af.create_algorithm(\"uuid\",\"uuid\")\r\n",
    "uuid = uuid.create_uuid()\r\n",
    "print(uuid)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "The last component of an algo/arrow Python interface is creating an abstract algorithm from python and exposing it to the Kwiver plugin loader. NOTE THIS FUNCTIONALITY IS NOT CURRENTLY SUPPORTED. This code block serves as an advertisement of upcoming features."
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "from kwiver.vital.algo import algorithm_factory as af, _algorithm\r\n",
    "from kwiver.vital import types as kvt\r\n",
    "\r\n",
    "class PyImageIO(_algorithm):\r\n",
    "    @static\r\n",
    "    def create(name: str) -> PyImageIO:\r\n",
    "        pass\r\n",
    "    @static\r\n",
    "    def registered_names() -> list:\r\n",
    "        pass\r\n",
    "    def type_name() -> str:\r\n",
    "        pass\r\n",
    "    @static\r\n",
    "    def get_nested_algo_configuration() -> None:\r\n",
    "        pass"
   ],
   "outputs": [],
   "metadata": {}
  }
 ]
}
